use foundry_test_utils::snapbox;

// tests complete bind-json workflow
// ensures that we can run forge-bind even if files are depending on yet non-existent bindings and
// that generated bindings are correct
forgetest_init!(test_bind_json, |prj, cmd| {
    prj.add_test(
        "JsonBindings",
        r#"
import {JsonBindings} from "utils/JsonBindings.sol";
import {Test} from "forge-std/Test.sol";

struct TopLevelStruct {
    uint256 param1;
    int8 param2;
}

contract BindJsonTest is Test {
    using JsonBindings for *;

    struct ContractLevelStruct {
        address[][] param1;
        address addrParam;
    }

    function testTopLevel() public pure {
        string memory json = '{"param1": 1, "param2": -1}';
        TopLevelStruct memory topLevel = json.deserializeTopLevelStruct();
        assertEq(topLevel.param1, 1);
        assertEq(topLevel.param2, -1);

        json = topLevel.serialize();
        TopLevelStruct memory deserialized = json.deserializeTopLevelStruct();
        assertEq(keccak256(abi.encode(deserialized)), keccak256(abi.encode(topLevel)));
    }

    function testContractLevel() public pure {
        ContractLevelStruct memory contractLevel = ContractLevelStruct({
            param1: new address[][](2),
            addrParam: address(0xBEEF)
        });

        string memory json = contractLevel.serialize();
        assertEq(json, '{"param1":[[],[]],"addrParam":"0x000000000000000000000000000000000000bEEF"}');

        ContractLevelStruct memory deserialized = json.deserializeContractLevelStruct();
        assertEq(keccak256(abi.encode(deserialized)), keccak256(abi.encode(contractLevel)));
    }
}
"#,
    );

    cmd.arg("bind-json").assert_success();

    snapbox::assert_data_eq!(
        snapbox::Data::read_from(&prj.root().join("utils/JsonBindings.sol"), None),
        snapbox::str![[r#"
// Automatically generated by forge bind-json.

pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;

import {BindJsonTest, TopLevelStruct} from "test/JsonBindings.sol";

interface Vm {
    function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
    function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory);
    function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
    function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);
    function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);
}
...
library JsonBindings {
    Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

    string constant schema_TopLevelStruct = "TopLevelStruct(uint256 param1,int8 param2)";
    string constant schema_ContractLevelStruct = "ContractLevelStruct(address[][] param1,address addrParam)";

    function serialize(TopLevelStruct memory value) internal pure returns (string memory) {
        return vm.serializeJsonType(schema_TopLevelStruct, abi.encode(value));
    }

    function serialize(TopLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
        return vm.serializeJsonType(objectKey, valueKey, schema_TopLevelStruct, abi.encode(value));
    }

    function deserializeTopLevelStruct(string memory json) public pure returns (TopLevelStruct memory) {
        return abi.decode(vm.parseJsonType(json, schema_TopLevelStruct), (TopLevelStruct));
    }

    function deserializeTopLevelStruct(string memory json, string memory path) public pure returns (TopLevelStruct memory) {
        return abi.decode(vm.parseJsonType(json, path, schema_TopLevelStruct), (TopLevelStruct));
    }

    function deserializeTopLevelStructArray(string memory json, string memory path) public pure returns (TopLevelStruct[] memory) {
        return abi.decode(vm.parseJsonTypeArray(json, path, schema_TopLevelStruct), (TopLevelStruct[]));
    }

    function serialize(BindJsonTest.ContractLevelStruct memory value) internal pure returns (string memory) {
        return vm.serializeJsonType(schema_ContractLevelStruct, abi.encode(value));
    }

    function serialize(BindJsonTest.ContractLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
        return vm.serializeJsonType(objectKey, valueKey, schema_ContractLevelStruct, abi.encode(value));
    }

    function deserializeContractLevelStruct(string memory json) public pure returns (BindJsonTest.ContractLevelStruct memory) {
        return abi.decode(vm.parseJsonType(json, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct));
    }

    function deserializeContractLevelStruct(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct memory) {
        return abi.decode(vm.parseJsonType(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct));
    }

    function deserializeContractLevelStructArray(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct[] memory) {
        return abi.decode(vm.parseJsonTypeArray(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct[]));
    }
}

"#]],
    );

    cmd.forge_fuse().args(["test"]).assert_success();
});
